Vamos a partir de lo que hemos visto en el notebook anterior...

Lectura de un fichero de datos


In [ ]:
# primero hacemos los imports de turno
import os
import datetime as dt

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(19760812)
%matplotlib inline

In [ ]:
# Leemos los datos del fichero 'mast.txt'
ipath = os.path.join('Datos', 'mast.txt')

def dateparse(date, time):
    YY = 2000 + int(date[:2])
    MM = int(date[2:4])
    DD = int(date[4:])
    hh = int(time[:2])
    mm = int(time[2:])
    
    return dt.datetime(YY, MM, DD, hh, mm, 0)
    

cols = ['Date', 'time', 'wspd', 'wspd_max', 'wdir',
        'x1', 'x2', 'x3', 'x4', 'x5', 
        'wspd_std']
wind = pd.read_csv(ipath, sep = "\s*", names = cols, 
                   parse_dates = [[0, 1]], index_col = 0,
                   date_parser = dateparse)

Información básica (en este caso de un DataFrame)


In [ ]:
wind.info()

In [ ]:
wind.describe()

Acceso a los índices, valores, columnas (las Series no disponen de este atributo):


In [ ]:
wind.index

In [ ]:
wind.values

In [ ]:
wind.values.shape

In [ ]:
wind.columns

Eliminando/extrayendo columnas

Hay varias columnas que no nos interesan (las que hemos llamado x1, x2, x3, x4 y x5). Vamos a eliminarlas de nuestro DataFrame. Lo podemos hacer de varias formas:


In [ ]:
# eliminamos una columna usando la keyword 'del'
del wind['x1']
wind.head(3)

In [ ]:
# Extraemos una columna usando el método pop
s = wind.pop('x2')
wind.head(3)

In [ ]:
del wind['x3']
del wind['x4']
del wind['x5']

In [ ]:
wind.info()

Una de las columnas, la que hemos extraído con el método pop, la hemos almacenado en la variable s, que es una Series:


In [ ]:
type(s)

In [ ]:
s.head(3)

In [ ]:
s.info()

¿Es una TimeSeries?, i.e., ¿todos los índices son fechas?


In [ ]:
# s.is_time_series deprecated
s.index.is_all_dates

In [ ]:
s.describe()

In [ ]:
s.dtype

In [ ]:
s.values

In [ ]:
s.index

In [ ]:
s.columns

Trabajando con los índices


In [ ]:
# Creamos un DataFrame
df = pd.DataFrame(np.array([['a','b','c','d','e'], [10,20,30,40,50]]).T,
                  columns = ['col1', 'col2'])
df

Los índices se pueden reescribir en cualquier momento:


In [ ]:
df.index = np.arange(1,6) * 100
df

Podemos usar una columna para definir nuestros índices:


In [ ]:
df.set_index('col1', inplace = True)
df

Podemos deshacer lo anterior usando:


In [ ]:
df.reset_index(inplace = True)
df

También podemos cambiar los nombres de las columnas:


In [ ]:
df.columns = ['column1', 'column2']
df

Podemos darle un nombre a la columna de índices (como hemos visto anteriormente):


In [ ]:
df.index.name = 'indices'
df

Las estructuras de datos de pandas son numpy arrays con esteroides

No olvidemos que entre bambalinas tenemos numpy arrays y pandas expone mucha de la funcionalidad de los numpy arrays directamente dentro de sus propias estructuras de datos.

Vemos, por ejemplo, qué atributos de un numpy array están disponibles directamente en una Series (o en un DataFrame):


In [ ]:
numpy_attrs = dir(s.values)
series_attrs = dir(s)
for attr in numpy_attrs:
    if attr not in series_attrs:
        print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
    else:
        print(attr)

Por tanto, muchas operaciones que hacemos con un numpy array la tenemos directamente disponible con una estructura de datos de Pandas:


In [ ]:
s.mean()

In [ ]:
s.min()

In [ ]:
s.max()

In [ ]:
s[0:10].tolist()

...

Nota:

Hay momentos en que resultará conveniente usar directamente los métodos de los numpy arrays si el rendimiento es un problema.


In [ ]:
%%timeit 
s.mean()
s.min()
s.max()

In [ ]:
%%timeit 
s.values.mean()
s.values.min()
s.values.max()

¿Y dónde están los esteroides?

Sed pacientes!!!!!!

'Cosas' que están en una Series pero no en un numpy array


In [ ]:
numpy_attrs = dir(s.values)
series_attrs = dir(s)
for attr in series_attrs:
    if attr not in numpy_attrs:
        print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
    else:
        print(attr)

'Cosas' que están en un DataFrame pero no en un numpy array.


In [ ]:
numpy_attrs = dir(s.values)
dataframe_attrs = dir(wind)
for attr in dataframe_attrs:
    if attr not in numpy_attrs:
        print('NOOOOOOOOOOOOOOOOOOOOOO', attr)
    else:
        print(attr)

Ejemplos de algunas operaciones muy útiles.

Veremos estas cosas más en detalle y con ejemplos en próximos notebooks.


In [ ]:
wind['wspd'].apply(lambda x: str(x) + ' m/s')

In [ ]:
wind.corr()

In [ ]:
wind.cumsum()

In [ ]:
wind.diff()

De momento, hasta que no veamos como hacer una serie de operaciones, estamos pasando por todo esto por encima. Luego veremos algunas cosas más en detalle...

Hagamos algunos ejemplos sencillos:


In [ ]:
# obtened la velocidad media del viento (columna 'wspd'):

In [ ]:
# obtened la mediana de la dirección del viento (columna 'wdir'):

In [ ]:
# obtened el valor máximo de la diferencia entre dos 
# pasos temporales de la desviación estándar de la velocidad (columna 'wspd_std')

Otros métodos interesantes son los pd.rolling_*:


In [ ]:
pd.rolling_mean(wind, 5, center = True).head(10)

Como podéis leer en el mensaje de error estas funciones rolling_* dejarán de existir en el futuro. En la celda de texto anterior he hablado de métodos de forma explícita porque todas las funciones rolling_* se han agrupado en el método rolling. Veamos lo anterior con la nueva forma:


In [ ]:
wind.rolling(5, center = True).mean().head(10)

Otras cosas a las que les podéis echar un ojo en un DataFrame (cambiad DataFrame por Series o cualquier otra estructura de datos de vuestro interés):


In [ ]:
import inspect
info = inspect.getmembers(wind, predicate=inspect.ismethod)

for stuff in info:
    print(stuff[0])

Trabajando con datos perdidos

Es normal que en nuestros datos tengamos valores perdidos.


In [ ]:
index = pd.date_range('2000/01/01', freq = '12H', periods = 10)
index = index.append(pd.date_range('2000/01/10', freq = '1D', periods = 3))
df = pd.DataFrame(np.random.randint(1, 100, size = (13, 3)), 
                  index = index, columns = ['col1', 'col2', 'col3'])
df

In [ ]:
# Vamos a rellenar algunos valores con NaN
df[df > 70] = np.nan
df

A diferencia de lo que sucede con un numpy array, en pandas, las operaciones con NaN ignoran los mismos. Veamos esto en acción:


In [ ]:
df['col1'].sum()

In [ ]:
df['col1'].values.sum()

In [ ]:
df['col1'].sum(skipna = False)

Podemos detectar los valores nulos (NaN) usando isnull:


In [ ]:
df.isnull()

O los no nulos usando notnull:


In [ ]:
df.notnull()

Vemos que tenemos valores NaN. Estos los podemos rellenar usando ffill o bfill (similar a fillna(method = 'ffill') y a fillna(method = 'bfill'), respectivamente):


In [ ]:
# Recordemos como era nuestro DataFrame
df

In [ ]:
df.ffill()

In [ ]:
df.bfill()

In [ ]:
df.fillna(value = 'Kiko')

Creamos un nuevo DataFrame con un frecuencia de 12H en los índices.


In [ ]:
df = pd.DataFrame(np.random.randint(1, 100, size = (15, 3)), 
                  index = pd.date_range('2015/01/01', freq = '12H', periods = 15))
df

In [ ]:
df[df > 70] = 'Kiko'
df

Podemos eliminar las filas o columnas que tengan algún valor NaN, todos los valores NaN,...


In [ ]:
df[df == 'Kiko'] = np.nan
df

In [ ]:
# Eliminamos las filas donde algún valor de la fila sea NaN
# axis = 0 sería equivalente a axis = 'rows'
# Más tarede veremos más sobre esto!!!!
df.dropna(axis = 'rows')

In [ ]:
# Eliminamos las filas donde todos los valores de la fila sean NaN
df.iloc[2, :] = np.nan
df.dropna(axis = 'rows', how = 'all') # axis = 0 sería equivalente a axis = 'rows'

In [ ]:
# Eliminamos las columnas donde algún valor de la columna sea NaN
df.dropna(axis = 'columns', how = 'any') # axis = 1 sería equivalente a axis = 'columns'. Más sobre esto después!!!
                                         # how = 'any' es el valor por defecto por lo que no sería necesario añadirlo

In [ ]:
# Vamos a añadir una columna que no tenga NaNs y vamos a hacer a misma operación
df['col4'] = 9999
df.dropna(axis = 'columns', how = 'any')

In [ ]:
# Ahora vamos a añadir una columna donde todos los valores sean NaN
df['col5'] = np.nan
df.dropna(axis = 'columns', how = 'all')

Tambien podemos rellenar los valores NaN usando interpolate:


In [ ]:
df.interpolate()

Pero que ha pasado!!! ¿Por qué no ha interpolado?

Veamos como son las columnas


In [ ]:
df.info()

Vemos que las columnas 0, 1 y 2 son del tipo object y no son un número. Por otro lado, en la columna col4 no hay nada que interpolar puesto que no hay valores perdidos. Por último, la columna col5 son todo NaN por lo que no se puede inventar la información. Transformemos las tres primeras columnas e interpolemos:


In [ ]:
df[[0, 1, 2]] = df[[0, 1, 2]].astype(np.float)

In [ ]:
df.interpolate()

In [ ]:
# Mirad la ayuda del método interpolate para aprender mejor como usarlo